home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / urlgrabber / grabber.py < prev    next >
Encoding:
Text File  |  2010-07-08  |  66.0 KB  |  1,803 lines

  1. #   This library is free software; you can redistribute it and/or
  2. #   modify it under the terms of the GNU Lesser General Public
  3. #   License as published by the Free Software Foundation; either
  4. #   version 2.1 of the License, or (at your option) any later version.
  5. #
  6. #   This library is distributed in the hope that it will be useful,
  7. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  9. #   Lesser General Public License for more details.
  10. #
  11. #   You should have received a copy of the GNU Lesser General Public
  12. #   License along with this library; if not, write to the 
  13. #      Free Software Foundation, Inc., 
  14. #      59 Temple Place, Suite 330, 
  15. #      Boston, MA  02111-1307  USA
  16.  
  17. # This file is part of urlgrabber, a high-level cross-protocol url-grabber
  18. # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
  19. # Copyright 2009 Red Hat inc, pycurl code written by Seth Vidal
  20.  
  21. """A high-level cross-protocol url-grabber.
  22.  
  23. GENERAL ARGUMENTS (kwargs)
  24.  
  25.   Where possible, the module-level default is indicated, and legal
  26.   values are provided.
  27.  
  28.   copy_local = 0   [0|1]
  29.  
  30.     ignored except for file:// urls, in which case it specifies
  31.     whether urlgrab should still make a copy of the file, or simply
  32.     point to the existing copy. The module level default for this
  33.     option is 0.
  34.  
  35.   close_connection = 0   [0|1]
  36.  
  37.     tells URLGrabber to close the connection after a file has been
  38.     transfered. This is ignored unless the download happens with the
  39.     http keepalive handler (keepalive=1).  Otherwise, the connection
  40.     is left open for further use. The module level default for this
  41.     option is 0 (keepalive connections will not be closed).
  42.  
  43.   keepalive = 1   [0|1]
  44.  
  45.     specifies whether keepalive should be used for HTTP/1.1 servers
  46.     that support it. The module level default for this option is 1
  47.     (keepalive is enabled).
  48.  
  49.   progress_obj = None
  50.  
  51.     a class instance that supports the following methods:
  52.       po.start(filename, url, basename, length, text)
  53.       # length will be None if unknown
  54.       po.update(read) # read == bytes read so far
  55.       po.end()
  56.  
  57.   text = None
  58.   
  59.     specifies alternative text to be passed to the progress meter
  60.     object.  If not given, the default progress meter will use the
  61.     basename of the file.
  62.  
  63.   throttle = 1.0
  64.  
  65.     a number - if it's an int, it's the bytes/second throttle limit.
  66.     If it's a float, it is first multiplied by bandwidth.  If throttle
  67.     == 0, throttling is disabled.  If None, the module-level default
  68.     (which can be set on default_grabber.throttle) is used. See
  69.     BANDWIDTH THROTTLING for more information.
  70.  
  71.   timeout = 300
  72.  
  73.     a positive integer expressing the number of seconds to wait before
  74.     timing out attempts to connect to a server. If the value is None
  75.     or 0, connection attempts will not time out. The timeout is passed
  76.     to the underlying pycurl object as its CONNECTTIMEOUT option, see
  77.     the curl documentation on CURLOPT_CONNECTTIMEOUT for more information.
  78.     http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT
  79.  
  80.   bandwidth = 0
  81.  
  82.     the nominal max bandwidth in bytes/second.  If throttle is a float
  83.     and bandwidth == 0, throttling is disabled.  If None, the
  84.     module-level default (which can be set on
  85.     default_grabber.bandwidth) is used. See BANDWIDTH THROTTLING for
  86.     more information.
  87.  
  88.   range = None
  89.  
  90.     a tuple of the form (first_byte, last_byte) describing a byte
  91.     range to retrieve. Either or both of the values may set to
  92.     None. If first_byte is None, byte offset 0 is assumed. If
  93.     last_byte is None, the last byte available is assumed. Note that
  94.     the range specification is python-like in that (0,10) will yeild
  95.     the first 10 bytes of the file.
  96.  
  97.     If set to None, no range will be used.
  98.     
  99.   reget = None   [None|'simple'|'check_timestamp']
  100.  
  101.     whether to attempt to reget a partially-downloaded file.  Reget
  102.     only applies to .urlgrab and (obviously) only if there is a
  103.     partially downloaded file.  Reget has two modes:
  104.  
  105.       'simple' -- the local file will always be trusted.  If there
  106.         are 100 bytes in the local file, then the download will always
  107.         begin 100 bytes into the requested file.
  108.  
  109.       'check_timestamp' -- the timestamp of the server file will be
  110.         compared to the timestamp of the local file.  ONLY if the
  111.         local file is newer than or the same age as the server file
  112.         will reget be used.  If the server file is newer, or the
  113.         timestamp is not returned, the entire file will be fetched.
  114.  
  115.     NOTE: urlgrabber can do very little to verify that the partial
  116.     file on disk is identical to the beginning of the remote file.
  117.     You may want to either employ a custom "checkfunc" or simply avoid
  118.     using reget in situations where corruption is a concern.
  119.  
  120.   user_agent = 'urlgrabber/VERSION'
  121.  
  122.     a string, usually of the form 'AGENT/VERSION' that is provided to
  123.     HTTP servers in the User-agent header. The module level default
  124.     for this option is "urlgrabber/VERSION".
  125.  
  126.   http_headers = None
  127.  
  128.     a tuple of 2-tuples, each containing a header and value.  These
  129.     will be used for http and https requests only.  For example, you
  130.     can do
  131.       http_headers = (('Pragma', 'no-cache'),)
  132.  
  133.   ftp_headers = None
  134.  
  135.     this is just like http_headers, but will be used for ftp requests.
  136.  
  137.   proxies = None
  138.  
  139.     a dictionary that maps protocol schemes to proxy hosts. For
  140.     example, to use a proxy server on host "foo" port 3128 for http
  141.     and https URLs:
  142.       proxies={ 'http' : 'http://foo:3128', 'https' : 'http://foo:3128' }
  143.     note that proxy authentication information may be provided using
  144.     normal URL constructs:
  145.       proxies={ 'http' : 'http://user:host@foo:3128' }
  146.     Lastly, if proxies is None, the default environment settings will
  147.     be used.
  148.  
  149.   prefix = None
  150.  
  151.     a url prefix that will be prepended to all requested urls.  For
  152.     example:
  153.       g = URLGrabber(prefix='http://foo.com/mirror/')
  154.       g.urlgrab('some/file.txt')
  155.       ## this will fetch 'http://foo.com/mirror/some/file.txt'
  156.     This option exists primarily to allow identical behavior to
  157.     MirrorGroup (and derived) instances.  Note: a '/' will be inserted
  158.     if necessary, so you cannot specify a prefix that ends with a
  159.     partial file or directory name.
  160.  
  161.   opener = None
  162.     No-op when using the curl backend (default)
  163.  
  164.   cache_openers = True
  165.     No-op when using the curl backend (default)
  166.  
  167.   data = None
  168.  
  169.     Only relevant for the HTTP family (and ignored for other
  170.     protocols), this allows HTTP POSTs.  When the data kwarg is
  171.     present (and not None), an HTTP request will automatically become
  172.     a POST rather than GET.  This is done by direct passthrough to
  173.     urllib2.  If you use this, you may also want to set the
  174.     'Content-length' and 'Content-type' headers with the http_headers
  175.     option.  Note that python 2.2 handles the case of these
  176.     badly and if you do not use the proper case (shown here), your
  177.     values will be overridden with the defaults.
  178.     
  179.   urlparser = URLParser()
  180.  
  181.     The URLParser class handles pre-processing of URLs, including
  182.     auth-handling for user/pass encoded in http urls, file handing
  183.     (that is, filenames not sent as a URL), and URL quoting.  If you
  184.     want to override any of this behavior, you can pass in a
  185.     replacement instance.  See also the 'quote' option.
  186.  
  187.   quote = None
  188.  
  189.     Whether or not to quote the path portion of a url.
  190.       quote = 1    ->  quote the URLs (they're not quoted yet)
  191.       quote = 0    ->  do not quote them (they're already quoted)
  192.       quote = None ->  guess what to do
  193.  
  194.     This option only affects proper urls like 'file:///etc/passwd'; it
  195.     does not affect 'raw' filenames like '/etc/passwd'.  The latter
  196.     will always be quoted as they are converted to URLs.  Also, only
  197.     the path part of a url is quoted.  If you need more fine-grained
  198.     control, you should probably subclass URLParser and pass it in via
  199.     the 'urlparser' option.
  200.  
  201.   ssl_ca_cert = None
  202.  
  203.     this option can be used if M2Crypto is available and will be
  204.     ignored otherwise.  If provided, it will be used to create an SSL
  205.     context.  If both ssl_ca_cert and ssl_context are provided, then
  206.     ssl_context will be ignored and a new context will be created from
  207.     ssl_ca_cert.
  208.  
  209.   ssl_context = None
  210.  
  211.     No-op when using the curl backend (default)
  212.    
  213.  
  214.   self.ssl_verify_peer = True 
  215.  
  216.     Check the server's certificate to make sure it is valid with what our CA validates
  217.   
  218.   self.ssl_verify_host = True
  219.  
  220.     Check the server's hostname to make sure it matches the certificate DN
  221.  
  222.   self.ssl_key = None
  223.  
  224.     Path to the key the client should use to connect/authenticate with
  225.  
  226.   self.ssl_key_type = 'PEM' 
  227.  
  228.     PEM or DER - format of key
  229.      
  230.   self.ssl_cert = None
  231.  
  232.     Path to the ssl certificate the client should use to to authenticate with
  233.  
  234.   self.ssl_cert_type = 'PEM' 
  235.  
  236.     PEM or DER - format of certificate
  237.     
  238.   self.ssl_key_pass = None 
  239.  
  240.     password to access the ssl_key
  241.     
  242.   self.size = None
  243.  
  244.     size (in bytes) or Maximum size of the thing being downloaded. 
  245.     This is mostly to keep us from exploding with an endless datastream
  246.   
  247.   self.max_header_size = 2097152 
  248.  
  249.     Maximum size (in bytes) of the headers.
  250.     
  251.  
  252. RETRY RELATED ARGUMENTS
  253.  
  254.   retry = None
  255.  
  256.     the number of times to retry the grab before bailing.  If this is
  257.     zero, it will retry forever. This was intentional... really, it
  258.     was :). If this value is not supplied or is supplied but is None
  259.     retrying does not occur.
  260.  
  261.   retrycodes = [-1,2,4,5,6,7]
  262.  
  263.     a sequence of errorcodes (values of e.errno) for which it should
  264.     retry. See the doc on URLGrabError for more details on this.  You
  265.     might consider modifying a copy of the default codes rather than
  266.     building yours from scratch so that if the list is extended in the
  267.     future (or one code is split into two) you can still enjoy the
  268.     benefits of the default list.  You can do that with something like
  269.     this:
  270.  
  271.       retrycodes = urlgrabber.grabber.URLGrabberOptions().retrycodes
  272.       if 12 not in retrycodes:
  273.           retrycodes.append(12)
  274.       
  275.   checkfunc = None
  276.  
  277.     a function to do additional checks. This defaults to None, which
  278.     means no additional checking.  The function should simply return
  279.     on a successful check.  It should raise URLGrabError on an
  280.     unsuccessful check.  Raising of any other exception will be
  281.     considered immediate failure and no retries will occur.
  282.  
  283.     If it raises URLGrabError, the error code will determine the retry
  284.     behavior.  Negative error numbers are reserved for use by these
  285.     passed in functions, so you can use many negative numbers for
  286.     different types of failure.  By default, -1 results in a retry,
  287.     but this can be customized with retrycodes.
  288.  
  289.     If you simply pass in a function, it will be given exactly one
  290.     argument: a CallbackObject instance with the .url attribute
  291.     defined and either .filename (for urlgrab) or .data (for urlread).
  292.     For urlgrab, .filename is the name of the local file.  For
  293.     urlread, .data is the actual string data.  If you need other
  294.     arguments passed to the callback (program state of some sort), you
  295.     can do so like this:
  296.  
  297.       checkfunc=(function, ('arg1', 2), {'kwarg': 3})
  298.  
  299.     if the downloaded file has filename /tmp/stuff, then this will
  300.     result in this call (for urlgrab):
  301.  
  302.       function(obj, 'arg1', 2, kwarg=3)
  303.       # obj.filename = '/tmp/stuff'
  304.       # obj.url = 'http://foo.com/stuff'
  305.       
  306.     NOTE: both the "args" tuple and "kwargs" dict must be present if
  307.     you use this syntax, but either (or both) can be empty.
  308.  
  309.   failure_callback = None
  310.  
  311.     The callback that gets called during retries when an attempt to
  312.     fetch a file fails.  The syntax for specifying the callback is
  313.     identical to checkfunc, except for the attributes defined in the
  314.     CallbackObject instance.  The attributes for failure_callback are:
  315.  
  316.       exception = the raised exception
  317.       url       = the url we're trying to fetch
  318.       tries     = the number of tries so far (including this one)
  319.       retry     = the value of the retry option
  320.  
  321.     The callback is present primarily to inform the calling program of
  322.     the failure, but if it raises an exception (including the one it's
  323.     passed) that exception will NOT be caught and will therefore cause
  324.     future retries to be aborted.
  325.  
  326.     The callback is called for EVERY failure, including the last one.
  327.     On the last try, the callback can raise an alternate exception,
  328.     but it cannot (without severe trickiness) prevent the exception
  329.     from being raised.
  330.  
  331.   interrupt_callback = None
  332.  
  333.     This callback is called if KeyboardInterrupt is received at any
  334.     point in the transfer.  Basically, this callback can have three
  335.     impacts on the fetch process based on the way it exits:
  336.  
  337.       1) raise no exception: the current fetch will be aborted, but
  338.          any further retries will still take place
  339.  
  340.       2) raise a URLGrabError: if you're using a MirrorGroup, then
  341.          this will prompt a failover to the next mirror according to
  342.          the behavior of the MirrorGroup subclass.  It is recommended
  343.          that you raise URLGrabError with code 15, 'user abort'.  If
  344.          you are NOT using a MirrorGroup subclass, then this is the
  345.          same as (3).
  346.  
  347.       3) raise some other exception (such as KeyboardInterrupt), which
  348.          will not be caught at either the grabber or mirror levels.
  349.          That is, it will be raised up all the way to the caller.
  350.  
  351.     This callback is very similar to failure_callback.  They are
  352.     passed the same arguments, so you could use the same function for
  353.     both.
  354.       
  355. BANDWIDTH THROTTLING
  356.  
  357.   urlgrabber supports throttling via two values: throttle and
  358.   bandwidth Between the two, you can either specify and absolute
  359.   throttle threshold or specify a theshold as a fraction of maximum
  360.   available bandwidth.
  361.  
  362.   throttle is a number - if it's an int, it's the bytes/second
  363.   throttle limit.  If it's a float, it is first multiplied by
  364.   bandwidth.  If throttle == 0, throttling is disabled.  If None, the
  365.   module-level default (which can be set with set_throttle) is used.
  366.  
  367.   bandwidth is the nominal max bandwidth in bytes/second.  If throttle
  368.   is a float and bandwidth == 0, throttling is disabled.  If None, the
  369.   module-level default (which can be set with set_bandwidth) is used.
  370.  
  371.   THROTTLING EXAMPLES:
  372.  
  373.   Lets say you have a 100 Mbps connection.  This is (about) 10^8 bits
  374.   per second, or 12,500,000 Bytes per second.  You have a number of
  375.   throttling options:
  376.  
  377.   *) set_bandwidth(12500000); set_throttle(0.5) # throttle is a float
  378.  
  379.      This will limit urlgrab to use half of your available bandwidth.
  380.  
  381.   *) set_throttle(6250000) # throttle is an int
  382.  
  383.      This will also limit urlgrab to use half of your available
  384.      bandwidth, regardless of what bandwidth is set to.
  385.  
  386.   *) set_throttle(6250000); set_throttle(1.0) # float
  387.  
  388.      Use half your bandwidth
  389.  
  390.   *) set_throttle(6250000); set_throttle(2.0) # float
  391.  
  392.     Use up to 12,500,000 Bytes per second (your nominal max bandwidth)
  393.  
  394.   *) set_throttle(6250000); set_throttle(0) # throttle = 0
  395.  
  396.      Disable throttling - this is more efficient than a very large
  397.      throttle setting.
  398.  
  399.   *) set_throttle(0); set_throttle(1.0) # throttle is float, bandwidth = 0
  400.  
  401.      Disable throttling - this is the default when the module is loaded.
  402.  
  403.   SUGGESTED AUTHOR IMPLEMENTATION (THROTTLING)
  404.  
  405.   While this is flexible, it's not extremely obvious to the user.  I
  406.   suggest you implement a float throttle as a percent to make the
  407.   distinction between absolute and relative throttling very explicit.
  408.  
  409.   Also, you may want to convert the units to something more convenient
  410.   than bytes/second, such as kbps or kB/s, etc.
  411.  
  412. """
  413.  
  414.  
  415.  
  416. import os
  417. import sys
  418. import urlparse
  419. import time
  420. import string
  421. import urllib
  422. import urllib2
  423. import mimetools
  424. import thread
  425. import types
  426. import stat
  427. import pycurl
  428. from ftplib import parse150
  429. from StringIO import StringIO
  430. from httplib import HTTPException
  431. import socket
  432. from byterange import range_tuple_normalize, range_tuple_to_header, RangeError
  433.  
  434. ########################################################################
  435. #                     MODULE INITIALIZATION
  436. ########################################################################
  437. try:
  438.     exec('from ' + (__name__.split('.'))[0] + ' import __version__')
  439. except:
  440.     __version__ = '???'
  441.  
  442. try:
  443.     # this part isn't going to do much - need to talk to gettext
  444.     from i18n import _
  445. except ImportError, msg:
  446.     def _(st): return st
  447.     
  448. ########################################################################
  449. # functions for debugging output.  These functions are here because they
  450. # are also part of the module initialization.
  451. DEBUG = None
  452. def set_logger(DBOBJ):
  453.     """Set the DEBUG object.  This is called by _init_default_logger when
  454.     the environment variable URLGRABBER_DEBUG is set, but can also be
  455.     called by a calling program.  Basically, if the calling program uses
  456.     the logging module and would like to incorporate urlgrabber logging,
  457.     then it can do so this way.  It's probably not necessary as most
  458.     internal logging is only for debugging purposes.
  459.  
  460.     The passed-in object should be a logging.Logger instance.  It will
  461.     be pushed into the keepalive and byterange modules if they're
  462.     being used.  The mirror module pulls this object in on import, so
  463.     you will need to manually push into it.  In fact, you may find it
  464.     tidier to simply push your logging object (or objects) into each
  465.     of these modules independently.
  466.     """
  467.  
  468.     global DEBUG
  469.     DEBUG = DBOBJ
  470.  
  471. def _init_default_logger(logspec=None):
  472.     '''Examines the environment variable URLGRABBER_DEBUG and creates
  473.     a logging object (logging.logger) based on the contents.  It takes
  474.     the form
  475.  
  476.       URLGRABBER_DEBUG=level,filename
  477.       
  478.     where "level" can be either an integer or a log level from the
  479.     logging module (DEBUG, INFO, etc).  If the integer is zero or
  480.     less, logging will be disabled.  Filename is the filename where
  481.     logs will be sent.  If it is "-", then stdout will be used.  If
  482.     the filename is empty or missing, stderr will be used.  If the
  483.     variable cannot be processed or the logging module cannot be
  484.     imported (python < 2.3) then logging will be disabled.  Here are
  485.     some examples:
  486.  
  487.       URLGRABBER_DEBUG=1,debug.txt   # log everything to debug.txt
  488.       URLGRABBER_DEBUG=WARNING,-     # log warning and higher to stdout
  489.       URLGRABBER_DEBUG=INFO          # log info and higher to stderr
  490.       
  491.     This funtion is called during module initialization.  It is not
  492.     intended to be called from outside.  The only reason it is a
  493.     function at all is to keep the module-level namespace tidy and to
  494.     collect the code into a nice block.'''
  495.  
  496.     try:
  497.         if logspec is None:
  498.             logspec = os.environ['URLGRABBER_DEBUG']
  499.         dbinfo = logspec.split(',')
  500.         import logging
  501.         level = logging._levelNames.get(dbinfo[0], None)
  502.         if level is None: level = int(dbinfo[0])
  503.         if level < 1: raise ValueError()
  504.  
  505.         formatter = logging.Formatter('%(asctime)s %(message)s')
  506.         if len(dbinfo) > 1: filename = dbinfo[1]
  507.         else: filename = ''
  508.         if filename == '': handler = logging.StreamHandler(sys.stderr)
  509.         elif filename == '-': handler = logging.StreamHandler(sys.stdout)
  510.         else:  handler = logging.FileHandler(filename)
  511.         handler.setFormatter(formatter)
  512.         DBOBJ = logging.getLogger('urlgrabber')
  513.         DBOBJ.addHandler(handler)
  514.         DBOBJ.setLevel(level)
  515.     except (KeyError, ImportError, ValueError):
  516.         DBOBJ = None
  517.     set_logger(DBOBJ)
  518.  
  519. def _log_package_state():
  520.     if not DEBUG: return
  521.     DEBUG.info('urlgrabber version  = %s' % __version__)
  522.     DEBUG.info('trans function "_"  = %s' % _)
  523.         
  524. _init_default_logger()
  525. _log_package_state()
  526.  
  527.  
  528. # normally this would be from i18n or something like it ...
  529. def _(st):
  530.     return st
  531.  
  532. ########################################################################
  533. #                 END MODULE INITIALIZATION
  534. ########################################################################
  535.  
  536.  
  537.  
  538. class URLGrabError(IOError):
  539.     """
  540.     URLGrabError error codes:
  541.  
  542.       URLGrabber error codes (0 -- 255)
  543.         0    - everything looks good (you should never see this)
  544.         1    - malformed url
  545.         2    - local file doesn't exist
  546.         3    - request for non-file local file (dir, etc)
  547.         4    - IOError on fetch
  548.         5    - OSError on fetch
  549.         6    - no content length header when we expected one
  550.         7    - HTTPException
  551.         8    - Exceeded read limit (for urlread)
  552.         9    - Requested byte range not satisfiable.
  553.         10   - Byte range requested, but range support unavailable
  554.         11   - Illegal reget mode
  555.         12   - Socket timeout
  556.         13   - malformed proxy url
  557.         14   - HTTPError (includes .code and .exception attributes)
  558.         15   - user abort
  559.         16   - error writing to local file
  560.         
  561.       MirrorGroup error codes (256 -- 511)
  562.         256  - No more mirrors left to try
  563.  
  564.       Custom (non-builtin) classes derived from MirrorGroup (512 -- 767)
  565.         [ this range reserved for application-specific error codes ]
  566.  
  567.       Retry codes (< 0)
  568.         -1   - retry the download, unknown reason
  569.  
  570.     Note: to test which group a code is in, you can simply do integer
  571.     division by 256: e.errno / 256
  572.  
  573.     Negative codes are reserved for use by functions passed in to
  574.     retrygrab with checkfunc.  The value -1 is built in as a generic
  575.     retry code and is already included in the retrycodes list.
  576.     Therefore, you can create a custom check function that simply
  577.     returns -1 and the fetch will be re-tried.  For more customized
  578.     retries, you can use other negative number and include them in
  579.     retry-codes.  This is nice for outputting useful messages about
  580.     what failed.
  581.  
  582.     You can use these error codes like so:
  583.       try: urlgrab(url)
  584.       except URLGrabError, e:
  585.          if e.errno == 3: ...
  586.            # or
  587.          print e.strerror
  588.            # or simply
  589.          print e  #### print '[Errno %i] %s' % (e.errno, e.strerror)
  590.     """
  591.     def __init__(self, *args):
  592.         IOError.__init__(self, *args)
  593.         self.url = "No url specified"
  594.  
  595. class CallbackObject:
  596.     """Container for returned callback data.
  597.  
  598.     This is currently a dummy class into which urlgrabber can stuff
  599.     information for passing to callbacks.  This way, the prototype for
  600.     all callbacks is the same, regardless of the data that will be
  601.     passed back.  Any function that accepts a callback function as an
  602.     argument SHOULD document what it will define in this object.
  603.  
  604.     It is possible that this class will have some greater
  605.     functionality in the future.
  606.     """
  607.     def __init__(self, **kwargs):
  608.         self.__dict__.update(kwargs)
  609.  
  610. def urlgrab(url, filename=None, **kwargs):
  611.     """grab the file at <url> and make a local copy at <filename>
  612.     If filename is none, the basename of the url is used.
  613.     urlgrab returns the filename of the local file, which may be different
  614.     from the passed-in filename if the copy_local kwarg == 0.
  615.     
  616.     See module documentation for a description of possible kwargs.
  617.     """
  618.     return default_grabber.urlgrab(url, filename, **kwargs)
  619.  
  620. def urlopen(url, **kwargs):
  621.     """open the url and return a file object
  622.     If a progress object or throttle specifications exist, then
  623.     a special file object will be returned that supports them.
  624.     The file object can be treated like any other file object.
  625.     
  626.     See module documentation for a description of possible kwargs.
  627.     """
  628.     return default_grabber.urlopen(url, **kwargs)
  629.  
  630. def urlread(url, limit=None, **kwargs):
  631.     """read the url into a string, up to 'limit' bytes
  632.     If the limit is exceeded, an exception will be thrown.  Note that urlread
  633.     is NOT intended to be used as a way of saying "I want the first N bytes"
  634.     but rather 'read the whole file into memory, but don't use too much'
  635.     
  636.     See module documentation for a description of possible kwargs.
  637.     """
  638.     return default_grabber.urlread(url, limit, **kwargs)
  639.  
  640.  
  641. class URLParser:
  642.     """Process the URLs before passing them to urllib2.
  643.  
  644.     This class does several things:
  645.  
  646.       * add any prefix
  647.       * translate a "raw" file to a proper file: url
  648.       * handle any http or https auth that's encoded within the url
  649.       * quote the url
  650.  
  651.     Only the "parse" method is called directly, and it calls sub-methods.
  652.  
  653.     An instance of this class is held in the options object, which
  654.     means that it's easy to change the behavior by sub-classing and
  655.     passing the replacement in.  It need only have a method like:
  656.  
  657.         url, parts = urlparser.parse(url, opts)
  658.     """
  659.  
  660.     def parse(self, url, opts):
  661.         """parse the url and return the (modified) url and its parts
  662.  
  663.         Note: a raw file WILL be quoted when it's converted to a URL.
  664.         However, other urls (ones which come with a proper scheme) may
  665.         or may not be quoted according to opts.quote
  666.  
  667.           opts.quote = 1     --> quote it
  668.           opts.quote = 0     --> do not quote it
  669.           opts.quote = None  --> guess
  670.         """
  671.         quote = opts.quote
  672.         
  673.         if opts.prefix:
  674.             url = self.add_prefix(url, opts.prefix)
  675.             
  676.         parts = urlparse.urlparse(url)
  677.         (scheme, host, path, parm, query, frag) = parts
  678.  
  679.         if not scheme or (len(scheme) == 1 and scheme in string.letters):
  680.             # if a scheme isn't specified, we guess that it's "file:"
  681.             if url[0] not in '/\\': url = os.path.abspath(url)
  682.             url = 'file:' + urllib.pathname2url(url)
  683.             parts = urlparse.urlparse(url)
  684.             quote = 0 # pathname2url quotes, so we won't do it again
  685.             
  686.         if scheme in ['http', 'https']:
  687.             parts = self.process_http(parts, url)
  688.             
  689.         if quote is None:
  690.             quote = self.guess_should_quote(parts)
  691.         if quote:
  692.             parts = self.quote(parts)
  693.         
  694.         url = urlparse.urlunparse(parts)
  695.         return url, parts
  696.  
  697.     def add_prefix(self, url, prefix):
  698.         if prefix[-1] == '/' or url[0] == '/':
  699.             url = prefix + url
  700.         else:
  701.             url = prefix + '/' + url
  702.         return url
  703.  
  704.     def process_http(self, parts, url):
  705.         (scheme, host, path, parm, query, frag) = parts
  706.         # TODO: auth-parsing here, maybe? pycurl doesn't really need it
  707.         return (scheme, host, path, parm, query, frag)
  708.  
  709.     def quote(self, parts):
  710.         """quote the URL
  711.  
  712.         This method quotes ONLY the path part.  If you need to quote
  713.         other parts, you should override this and pass in your derived
  714.         class.  The other alternative is to quote other parts before
  715.         passing into urlgrabber.
  716.         """
  717.         (scheme, host, path, parm, query, frag) = parts
  718.         path = urllib.quote(path)
  719.         return (scheme, host, path, parm, query, frag)
  720.  
  721.     hexvals = '0123456789ABCDEF'
  722.     def guess_should_quote(self, parts):
  723.         """
  724.         Guess whether we should quote a path.  This amounts to
  725.         guessing whether it's already quoted.
  726.  
  727.         find ' '   ->  1
  728.         find '%'   ->  1
  729.         find '%XX' ->  0
  730.         else       ->  1
  731.         """
  732.         (scheme, host, path, parm, query, frag) = parts
  733.         if ' ' in path:
  734.             return 1
  735.         ind = string.find(path, '%')
  736.         if ind > -1:
  737.             while ind > -1:
  738.                 if len(path) < ind+3:
  739.                     return 1
  740.                 code = path[ind+1:ind+3].upper()
  741.                 if     code[0] not in self.hexvals or \
  742.                        code[1] not in self.hexvals:
  743.                     return 1
  744.                 ind = string.find(path, '%', ind+1)
  745.             return 0
  746.         return 1
  747.     
  748. class URLGrabberOptions:
  749.     """Class to ease kwargs handling."""
  750.  
  751.     def __init__(self, delegate=None, **kwargs):
  752.         """Initialize URLGrabberOptions object.
  753.         Set default values for all options and then update options specified
  754.         in kwargs.
  755.         """
  756.         self.delegate = delegate
  757.         if delegate is None:
  758.             self._set_defaults()
  759.         self._set_attributes(**kwargs)
  760.     
  761.     def __getattr__(self, name):
  762.         if self.delegate and hasattr(self.delegate, name):
  763.             return getattr(self.delegate, name)
  764.         raise AttributeError, name
  765.     
  766.     def raw_throttle(self):
  767.         """Calculate raw throttle value from throttle and bandwidth 
  768.         values.
  769.         """
  770.         if self.throttle <= 0:  
  771.             return 0
  772.         elif type(self.throttle) == type(0): 
  773.             return float(self.throttle)
  774.         else: # throttle is a float
  775.             return self.bandwidth * self.throttle
  776.         
  777.     def derive(self, **kwargs):
  778.         """Create a derived URLGrabberOptions instance.
  779.         This method creates a new instance and overrides the
  780.         options specified in kwargs.
  781.         """
  782.         return URLGrabberOptions(delegate=self, **kwargs)
  783.         
  784.     def _set_attributes(self, **kwargs):
  785.         """Update object attributes with those provided in kwargs."""
  786.         self.__dict__.update(kwargs)
  787.         if kwargs.has_key('range'):
  788.             # normalize the supplied range value
  789.             self.range = range_tuple_normalize(self.range)
  790.         if not self.reget in [None, 'simple', 'check_timestamp']:
  791.             raise URLGrabError(11, _('Illegal reget mode: %s') \
  792.                                % (self.reget, ))
  793.  
  794.     def _set_defaults(self):
  795.         """Set all options to their default values. 
  796.         When adding new options, make sure a default is
  797.         provided here.
  798.         """
  799.         self.progress_obj = None
  800.         self.throttle = 1.0
  801.         self.bandwidth = 0
  802.         self.retry = None
  803.         self.retrycodes = [-1,2,4,5,6,7]
  804.         self.checkfunc = None
  805.         self.copy_local = 0
  806.         self.close_connection = 0
  807.         self.range = None
  808.         self.user_agent = 'urlgrabber/%s' % __version__
  809.         self.keepalive = 1
  810.         self.proxies = None
  811.         self.reget = None
  812.         self.failure_callback = None
  813.         self.interrupt_callback = None
  814.         self.prefix = None
  815.         self.opener = None
  816.         self.cache_openers = True
  817.         self.timeout = 300
  818.         self.text = None
  819.         self.http_headers = None
  820.         self.ftp_headers = None
  821.         self.data = None
  822.         self.urlparser = URLParser()
  823.         self.quote = None
  824.         self.ssl_ca_cert = None # sets SSL_CAINFO - path to certdb
  825.         self.ssl_context = None # no-op in pycurl
  826.         self.ssl_verify_peer = True # check peer's cert for authenticityb
  827.         self.ssl_verify_host = True # make sure who they are and who the cert is for matches
  828.         self.ssl_key = None # client key
  829.         self.ssl_key_type = 'PEM' #(or DER)
  830.         self.ssl_cert = None # client cert
  831.         self.ssl_cert_type = 'PEM' # (or DER)
  832.         self.ssl_key_pass = None # password to access the key
  833.         self.size = None # if we know how big the thing we're getting is going
  834.                          # to be. this is ultimately a MAXIMUM size for the file
  835.         self.max_header_size = 2097152 #2mb seems reasonable for maximum header size
  836.         
  837.     def __repr__(self):
  838.         return self.format()
  839.         
  840.     def format(self, indent='  '):
  841.         keys = self.__dict__.keys()
  842.         if self.delegate is not None:
  843.             keys.remove('delegate')
  844.         keys.sort()
  845.         s = '{\n'
  846.         for k in keys:
  847.             s = s + indent + '%-15s: %s,\n' % \
  848.                 (repr(k), repr(self.__dict__[k]))
  849.         if self.delegate:
  850.             df = self.delegate.format(indent + '  ')
  851.             s = s + indent + '%-15s: %s\n' % ("'delegate'", df)
  852.         s = s + indent + '}'
  853.         return s
  854.  
  855. class URLGrabber:
  856.     """Provides easy opening of URLs with a variety of options.
  857.     
  858.     All options are specified as kwargs. Options may be specified when
  859.     the class is created and may be overridden on a per request basis.
  860.     
  861.     New objects inherit default values from default_grabber.
  862.     """
  863.     
  864.     def __init__(self, **kwargs):
  865.         self.opts = URLGrabberOptions(**kwargs)
  866.     
  867.     def _retry(self, opts, func, *args):
  868.         tries = 0
  869.         while 1:
  870.             # there are only two ways out of this loop.  The second has
  871.             # several "sub-ways"
  872.             #   1) via the return in the "try" block
  873.             #   2) by some exception being raised
  874.             #      a) an excepton is raised that we don't "except"
  875.             #      b) a callback raises ANY exception
  876.             #      c) we're not retry-ing or have run out of retries
  877.             #      d) the URLGrabError code is not in retrycodes
  878.             # beware of infinite loops :)
  879.             tries = tries + 1
  880.             exception = None
  881.             retrycode = None
  882.             callback  = None
  883.             if DEBUG: DEBUG.info('attempt %i/%s: %s',
  884.                                  tries, opts.retry, args[0])
  885.             try:
  886.                 r = apply(func, (opts,) + args, {})
  887.                 if DEBUG: DEBUG.info('success')
  888.                 return r
  889.             except URLGrabError, e:
  890.                 exception = e
  891.                 callback = opts.failure_callback
  892.                 retrycode = e.errno
  893.             except KeyboardInterrupt, e:
  894.                 exception = e
  895.                 callback = opts.interrupt_callback
  896.  
  897.             if DEBUG: DEBUG.info('exception: %s', exception)
  898.             if callback:
  899.                 if DEBUG: DEBUG.info('calling callback: %s', callback)
  900.                 cb_func, cb_args, cb_kwargs = self._make_callback(callback)
  901.                 obj = CallbackObject(exception=exception, url=args[0],
  902.                                      tries=tries, retry=opts.retry)
  903.                 cb_func(obj, *cb_args, **cb_kwargs)
  904.  
  905.             if (opts.retry is None) or (tries == opts.retry):
  906.                 if DEBUG: DEBUG.info('retries exceeded, re-raising')
  907.                 raise
  908.  
  909.             if (retrycode is not None) and (retrycode not in opts.retrycodes):
  910.                 if DEBUG: DEBUG.info('retrycode (%i) not in list %s, re-raising',
  911.                                      retrycode, opts.retrycodes)
  912.                 raise
  913.     
  914.     def urlopen(self, url, **kwargs):
  915.         """open the url and return a file object
  916.         If a progress object or throttle value specified when this 
  917.         object was created, then  a special file object will be 
  918.         returned that supports them. The file object can be treated 
  919.         like any other file object.
  920.         """
  921.         opts = self.opts.derive(**kwargs)
  922.         if DEBUG: DEBUG.debug('combined options: %s' % repr(opts))
  923.         (url,parts) = opts.urlparser.parse(url, opts) 
  924.         def retryfunc(opts, url):
  925.             return PyCurlFileObject(url, filename=None, opts=opts)
  926.         return self._retry(opts, retryfunc, url)
  927.     
  928.     def urlgrab(self, url, filename=None, **kwargs):
  929.         """grab the file at <url> and make a local copy at <filename>
  930.         If filename is none, the basename of the url is used.
  931.         urlgrab returns the filename of the local file, which may be 
  932.         different from the passed-in filename if copy_local == 0.
  933.         """
  934.         opts = self.opts.derive(**kwargs)
  935.         if DEBUG: DEBUG.debug('combined options: %s' % repr(opts))
  936.         (url,parts) = opts.urlparser.parse(url, opts) 
  937.         (scheme, host, path, parm, query, frag) = parts
  938.         if filename is None:
  939.             filename = os.path.basename( urllib.unquote(path) )
  940.         if scheme == 'file' and not opts.copy_local:
  941.             # just return the name of the local file - don't make a 
  942.             # copy currently
  943.             path = urllib.url2pathname(path)
  944.             if host:
  945.                 path = os.path.normpath('//' + host + path)
  946.             if not os.path.exists(path):
  947.                 err = URLGrabError(2, 
  948.                       _('Local file does not exist: %s') % (path, ))
  949.                 err.url = url
  950.                 raise err
  951.             elif not os.path.isfile(path):
  952.                 err = URLGrabError(3, 
  953.                                  _('Not a normal file: %s') % (path, ))
  954.                 err.url = url
  955.                 raise err
  956.  
  957.             elif not opts.range:
  958.                 if not opts.checkfunc is None:
  959.                     cb_func, cb_args, cb_kwargs = \
  960.                        self._make_callback(opts.checkfunc)
  961.                     obj = CallbackObject()
  962.                     obj.filename = path
  963.                     obj.url = url
  964.                     apply(cb_func, (obj, )+cb_args, cb_kwargs)        
  965.                 return path
  966.         
  967.         def retryfunc(opts, url, filename):
  968.             fo = PyCurlFileObject(url, filename, opts)
  969.             try:
  970.                 fo._do_grab()
  971.                 if not opts.checkfunc is None:
  972.                     cb_func, cb_args, cb_kwargs = \
  973.                              self._make_callback(opts.checkfunc)
  974.                     obj = CallbackObject()
  975.                     obj.filename = filename
  976.                     obj.url = url
  977.                     apply(cb_func, (obj, )+cb_args, cb_kwargs)
  978.             finally:
  979.                 fo.close()
  980.             return filename
  981.         
  982.         return self._retry(opts, retryfunc, url, filename)
  983.     
  984.     def urlread(self, url, limit=None, **kwargs):
  985.         """read the url into a string, up to 'limit' bytes
  986.         If the limit is exceeded, an exception will be thrown.  Note
  987.         that urlread is NOT intended to be used as a way of saying 
  988.         "I want the first N bytes" but rather 'read the whole file 
  989.         into memory, but don't use too much'
  990.         """
  991.         opts = self.opts.derive(**kwargs)
  992.         if DEBUG: DEBUG.debug('combined options: %s' % repr(opts))
  993.         (url,parts) = opts.urlparser.parse(url, opts) 
  994.         if limit is not None:
  995.             limit = limit + 1
  996.             
  997.         def retryfunc(opts, url, limit):
  998.             fo = PyCurlFileObject(url, filename=None, opts=opts)
  999.             s = ''
  1000.             try:
  1001.                 # this is an unfortunate thing.  Some file-like objects
  1002.                 # have a default "limit" of None, while the built-in (real)
  1003.                 # file objects have -1.  They each break the other, so for
  1004.                 # now, we just force the default if necessary.
  1005.                 if limit is None: s = fo.read()
  1006.                 else: s = fo.read(limit)
  1007.  
  1008.                 if not opts.checkfunc is None:
  1009.                     cb_func, cb_args, cb_kwargs = \
  1010.                              self._make_callback(opts.checkfunc)
  1011.                     obj = CallbackObject()
  1012.                     obj.data = s
  1013.                     obj.url = url
  1014.                     apply(cb_func, (obj, )+cb_args, cb_kwargs)
  1015.             finally:
  1016.                 fo.close()
  1017.             return s
  1018.             
  1019.         s = self._retry(opts, retryfunc, url, limit)
  1020.         if limit and len(s) > limit:
  1021.             err = URLGrabError(8, 
  1022.                                _('Exceeded limit (%i): %s') % (limit, url))
  1023.             err.url = url
  1024.             raise err
  1025.  
  1026.         return s
  1027.         
  1028.     def _make_callback(self, callback_obj):
  1029.         if callable(callback_obj):
  1030.             return callback_obj, (), {}
  1031.         else:
  1032.             return callback_obj
  1033.  
  1034. # create the default URLGrabber used by urlXXX functions.
  1035. # NOTE: actual defaults are set in URLGrabberOptions
  1036. default_grabber = URLGrabber()
  1037.  
  1038.  
  1039. class PyCurlFileObject():
  1040.     def __init__(self, url, filename, opts):
  1041.         self.fo = None
  1042.         self._hdr_dump = ''
  1043.         self._parsed_hdr = None
  1044.         self.url = url
  1045.         self.scheme = urlparse.urlsplit(self.url)[0]
  1046.         self.filename = filename
  1047.         self.append = False
  1048.         self.reget_time = None
  1049.         self.opts = opts
  1050.         if self.opts.reget == 'check_timestamp':
  1051.             raise NotImplementedError, "check_timestamp regets are not implemented in this ver of urlgrabber. Please report this."
  1052.         self._complete = False
  1053.         self._rbuf = ''
  1054.         self._rbufsize = 1024*8
  1055.         self._ttime = time.time()
  1056.         self._tsize = 0
  1057.         self._amount_read = 0
  1058.         self._reget_length = 0
  1059.         self._prog_running = False
  1060.         self._error = (None, None)
  1061.         self.size = 0
  1062.         self._hdr_ended = False
  1063.         self._do_open()
  1064.         
  1065.  
  1066.     def geturl(self):
  1067.         """ Provide the geturl() method, used to be got from
  1068.             urllib.addinfourl, via. urllib.URLopener.* """
  1069.         return self.url
  1070.         
  1071.     def __getattr__(self, name):
  1072.         """This effectively allows us to wrap at the instance level.
  1073.         Any attribute not found in _this_ object will be searched for
  1074.         in self.fo.  This includes methods."""
  1075.  
  1076.         if hasattr(self.fo, name):
  1077.             return getattr(self.fo, name)
  1078.         raise AttributeError, name
  1079.  
  1080.     def _retrieve(self, buf):
  1081.         try:
  1082.             if not self._prog_running:
  1083.                 if self.opts.progress_obj:
  1084.                     size  = self.size + self._reget_length
  1085.                     self.opts.progress_obj.start(self._prog_reportname, 
  1086.                                                  urllib.unquote(self.url), 
  1087.                                                  self._prog_basename, 
  1088.                                                  size=size,
  1089.                                                  text=self.opts.text)
  1090.                     self._prog_running = True
  1091.                     self.opts.progress_obj.update(self._amount_read)
  1092.  
  1093.             self._amount_read += len(buf)
  1094.             self.fo.write(buf)
  1095.             return len(buf)
  1096.         except KeyboardInterrupt:
  1097.             return -1
  1098.             
  1099.     def _hdr_retrieve(self, buf):
  1100.         if self._hdr_ended:
  1101.             self._hdr_dump = ''
  1102.             self.size = 0
  1103.             self._hdr_ended = False
  1104.  
  1105.         if self._over_max_size(cur=len(self._hdr_dump), 
  1106.                                max_size=self.opts.max_header_size):
  1107.             return -1
  1108.         try:
  1109.             self._hdr_dump += buf
  1110.             # we have to get the size before we do the progress obj start
  1111.             # but we can't do that w/o making it do 2 connects, which sucks
  1112.             # so we cheat and stuff it in here in the hdr_retrieve
  1113.             if self.scheme in ['http','https'] and buf.lower().find('content-length') != -1:
  1114.                 length = buf.split(':')[1]
  1115.                 self.size = int(length)
  1116.             elif self.scheme in ['ftp']:
  1117.                 s = None
  1118.                 if buf.startswith('213 '):
  1119.                     s = buf[3:].strip()
  1120.                 elif buf.startswith('150 '):
  1121.                     s = parse150(buf)
  1122.                 if s:
  1123.                     self.size = int(s)
  1124.                     
  1125.             if buf.lower().find('location') != -1:
  1126.                 location = ':'.join(buf.split(':')[1:])
  1127.                 location = location.strip()
  1128.                 self.scheme = urlparse.urlsplit(location)[0]
  1129.                 self.url = location
  1130.                 
  1131.             if len(self._hdr_dump) != 0 and buf == '\r\n':
  1132.                 self._hdr_ended = True
  1133.                 if DEBUG: DEBUG.info('header ended:')
  1134.                 
  1135.             return len(buf)
  1136.         except KeyboardInterrupt:
  1137.             return pycurl.READFUNC_ABORT
  1138.  
  1139.     def _return_hdr_obj(self):
  1140.         if self._parsed_hdr:
  1141.             return self._parsed_hdr
  1142.         statusend = self._hdr_dump.find('\n')
  1143.         statusend += 1 # ridiculous as it may seem.
  1144.         hdrfp = StringIO()
  1145.         hdrfp.write(self._hdr_dump[statusend:])
  1146.         hdrfp.seek(0)
  1147.         self._parsed_hdr =  mimetools.Message(hdrfp)
  1148.         return self._parsed_hdr
  1149.     
  1150.     hdr = property(_return_hdr_obj)
  1151.     http_code = property(fget=
  1152.                  lambda self: self.curl_obj.getinfo(pycurl.RESPONSE_CODE))
  1153.  
  1154.     def _set_opts(self, opts={}):
  1155.         # XXX
  1156.         if not opts:
  1157.             opts = self.opts
  1158.  
  1159.  
  1160.         # defaults we're always going to set
  1161.         self.curl_obj.setopt(pycurl.NOPROGRESS, False)
  1162.         self.curl_obj.setopt(pycurl.NOSIGNAL, True)
  1163.         self.curl_obj.setopt(pycurl.WRITEFUNCTION, self._retrieve)
  1164.         self.curl_obj.setopt(pycurl.HEADERFUNCTION, self._hdr_retrieve)
  1165.         self.curl_obj.setopt(pycurl.PROGRESSFUNCTION, self._progress_update)
  1166.         self.curl_obj.setopt(pycurl.FAILONERROR, True)
  1167.         self.curl_obj.setopt(pycurl.OPT_FILETIME, True)
  1168.         self.curl_obj.setopt(pycurl.FOLLOWLOCATION, True)
  1169.         
  1170.         if DEBUG:
  1171.             self.curl_obj.setopt(pycurl.VERBOSE, True)
  1172.         if opts.user_agent:
  1173.             self.curl_obj.setopt(pycurl.USERAGENT, opts.user_agent)
  1174.         
  1175.         # maybe to be options later
  1176.         self.curl_obj.setopt(pycurl.FOLLOWLOCATION, True)
  1177.         self.curl_obj.setopt(pycurl.MAXREDIRS, 5)
  1178.         
  1179.         # timeouts
  1180.         timeout = 300
  1181.         if hasattr(opts, 'timeout'):
  1182.             timeout = int(opts.timeout or 0)
  1183.         self.curl_obj.setopt(pycurl.CONNECTTIMEOUT, timeout)
  1184.         self.curl_obj.setopt(pycurl.LOW_SPEED_LIMIT, 1)
  1185.         self.curl_obj.setopt(pycurl.LOW_SPEED_TIME, timeout)
  1186.  
  1187.         # ssl options
  1188.         if self.scheme == 'https':
  1189.             if opts.ssl_ca_cert: # this may do ZERO with nss  according to curl docs
  1190.                 self.curl_obj.setopt(pycurl.CAPATH, opts.ssl_ca_cert)
  1191.                 self.curl_obj.setopt(pycurl.CAINFO, opts.ssl_ca_cert)
  1192.             self.curl_obj.setopt(pycurl.SSL_VERIFYPEER, opts.ssl_verify_peer)
  1193.             self.curl_obj.setopt(pycurl.SSL_VERIFYHOST, opts.ssl_verify_host)
  1194.             if opts.ssl_key:
  1195.                 self.curl_obj.setopt(pycurl.SSLKEY, opts.ssl_key)
  1196.             if opts.ssl_key_type:
  1197.                 self.curl_obj.setopt(pycurl.SSLKEYTYPE, opts.ssl_key_type)
  1198.             if opts.ssl_cert:
  1199.                 self.curl_obj.setopt(pycurl.SSLCERT, opts.ssl_cert)
  1200.             if opts.ssl_cert_type:                
  1201.                 self.curl_obj.setopt(pycurl.SSLCERTTYPE, opts.ssl_cert_type)
  1202.             if opts.ssl_key_pass:
  1203.                 self.curl_obj.setopt(pycurl.SSLKEYPASSWD, opts.ssl_key_pass)
  1204.  
  1205.         #headers:
  1206.         if opts.http_headers and self.scheme in ('http', 'https'):
  1207.             headers = []
  1208.             for (tag, content) in opts.http_headers:
  1209.                 headers.append('%s:%s' % (tag, content))
  1210.             self.curl_obj.setopt(pycurl.HTTPHEADER, headers)
  1211.  
  1212.         # ranges:
  1213.         if opts.range or opts.reget:
  1214.             range_str = self._build_range()
  1215.             if range_str:
  1216.                 self.curl_obj.setopt(pycurl.RANGE, range_str)
  1217.             
  1218.         # throttle/bandwidth
  1219.         if hasattr(opts, 'raw_throttle') and opts.raw_throttle():
  1220.             self.curl_obj.setopt(pycurl.MAX_RECV_SPEED_LARGE, int(opts.raw_throttle()))
  1221.             
  1222.         # proxy settings
  1223.         if opts.proxies:
  1224.             for (scheme, proxy) in opts.proxies.items():
  1225.                 if self.scheme in ('ftp'): # only set the ftp proxy for ftp items
  1226.                     if scheme not in ('ftp'):
  1227.                         continue
  1228.                     else:
  1229.                         if proxy == '_none_': proxy = ""
  1230.                         self.curl_obj.setopt(pycurl.PROXY, proxy)
  1231.                 elif self.scheme in ('http', 'https'):
  1232.                     if scheme not in ('http', 'https'):
  1233.                         continue
  1234.                     else:
  1235.                         if proxy == '_none_': proxy = ""
  1236.                         self.curl_obj.setopt(pycurl.PROXY, proxy)
  1237.             
  1238.         # FIXME username/password/auth settings
  1239.  
  1240.         #posts - simple - expects the fields as they are
  1241.         if opts.data:
  1242.             self.curl_obj.setopt(pycurl.POST, True)
  1243.             self.curl_obj.setopt(pycurl.POSTFIELDS, self._to_utf8(opts.data))
  1244.             
  1245.         # our url
  1246.         self.curl_obj.setopt(pycurl.URL, self.url)
  1247.         
  1248.     
  1249.     def _do_perform(self):
  1250.         if self._complete:
  1251.             return
  1252.         
  1253.         try:
  1254.             self.curl_obj.perform()
  1255.         except pycurl.error, e:
  1256.             # XXX - break some of these out a bit more clearly
  1257.             # to other URLGrabErrors from 
  1258.             # http://curl.haxx.se/libcurl/c/libcurl-errors.html
  1259.             # this covers e.args[0] == 22 pretty well - which will be common
  1260.             
  1261.             code = self.http_code
  1262.             errcode = e.args[0]
  1263.             if self._error[0]:
  1264.                 errcode = self._error[0]
  1265.                 
  1266.             if errcode == 23 and code >= 200 and code < 299:
  1267.                 err = URLGrabError(15, _('User (or something) called abort %s: %s') % (self.url, e))
  1268.                 err.url = self.url
  1269.                 
  1270.                 # this is probably wrong but ultimately this is what happens
  1271.                 # we have a legit http code and a pycurl 'writer failed' code
  1272.                 # which almost always means something aborted it from outside
  1273.                 # since we cannot know what it is -I'm banking on it being
  1274.                 # a ctrl-c. XXXX - if there's a way of going back two raises to 
  1275.                 # figure out what aborted the pycurl process FIXME
  1276.                 raise KeyboardInterrupt
  1277.             
  1278.             elif errcode == 28:
  1279.                 err = URLGrabError(12, _('Timeout on %s: %s') % (self.url, e))
  1280.                 err.url = self.url
  1281.                 raise err
  1282.             elif errcode == 35:
  1283.                 msg = _("problem making ssl connection")
  1284.                 err = URLGrabError(14, msg)
  1285.                 err.url = self.url
  1286.                 raise err
  1287.             elif errcode == 37:
  1288.                 msg = _("Could not open/read %s") % (self.url)
  1289.                 err = URLGrabError(14, msg)
  1290.                 err.url = self.url
  1291.                 raise err
  1292.                 
  1293.             elif errcode == 42:
  1294.                 err = URLGrabError(15, _('User (or something) called abort %s: %s') % (self.url, e))
  1295.                 err.url = self.url
  1296.                 # this is probably wrong but ultimately this is what happens
  1297.                 # we have a legit http code and a pycurl 'writer failed' code
  1298.                 # which almost always means something aborted it from outside
  1299.                 # since we cannot know what it is -I'm banking on it being
  1300.                 # a ctrl-c. XXXX - if there's a way of going back two raises to 
  1301.                 # figure out what aborted the pycurl process FIXME
  1302.                 raise KeyboardInterrupt
  1303.                 
  1304.             elif errcode == 58:
  1305.                 msg = _("problem with the local client certificate")
  1306.                 err = URLGrabError(14, msg)
  1307.                 err.url = self.url
  1308.                 raise err
  1309.  
  1310.             elif errcode == 60:
  1311.                 msg = _("Peer cert cannot be verified or peer cert invalid")
  1312.                 err = URLGrabError(14, msg)
  1313.                 err.url = self.url
  1314.                 raise err
  1315.             
  1316.             elif errcode == 63:
  1317.                 if self._error[1]:
  1318.                     msg = self._error[1]
  1319.                 else:
  1320.                     msg = _("Max download size exceeded on %s") % (self.url)
  1321.                 err = URLGrabError(14, msg)
  1322.                 err.url = self.url
  1323.                 raise err
  1324.                     
  1325.             elif str(e.args[1]) == '' and self.http_code != 0: # fake it until you make it
  1326.                 if self.scheme in ['http', 'https']:
  1327.                     msg = 'HTTP Error %s : %s ' % (self.http_code, self.url)
  1328.                 elif self.scheme in ['ftp']:
  1329.                     msg = 'FTP Error %s : %s ' % (self.http_code, self.url)
  1330.                 else:
  1331.                     msg = "Unknown Error: URL=%s , scheme=%s" % (self.url, self.scheme)
  1332.             else:
  1333.                 msg = 'PYCURL ERROR %s - "%s"' % (errcode, str(e.args[1]))
  1334.                 code = errcode
  1335.             err = URLGrabError(14, msg)
  1336.             err.code = code
  1337.             err.exception = e
  1338.             raise err
  1339.         else:
  1340.             if self._error[1]:
  1341.                 msg = self._error[1]
  1342.                 err = URLGRabError(14, msg)
  1343.                 err.url = self.url
  1344.                 raise err
  1345.  
  1346.     def _do_open(self):
  1347.         self.curl_obj = _curl_cache
  1348.         self.curl_obj.reset() # reset all old settings away, just in case
  1349.         # setup any ranges
  1350.         self._set_opts()
  1351.         self._do_grab()
  1352.         return self.fo
  1353.  
  1354.     def _add_headers(self):
  1355.         pass
  1356.         
  1357.     def _build_range(self):
  1358.         reget_length = 0
  1359.         rt = None
  1360.         if self.opts.reget and type(self.filename) in types.StringTypes:
  1361.             # we have reget turned on and we're dumping to a file
  1362.             try:
  1363.                 s = os.stat(self.filename)
  1364.             except OSError:
  1365.                 pass
  1366.             else:
  1367.                 self.reget_time = s[stat.ST_MTIME]
  1368.                 reget_length = s[stat.ST_SIZE]
  1369.  
  1370.                 # Set initial length when regetting
  1371.                 self._amount_read = reget_length    
  1372.                 self._reget_length = reget_length # set where we started from, too
  1373.  
  1374.                 rt = reget_length, ''
  1375.                 self.append = 1
  1376.                 
  1377.         if self.opts.range:
  1378.             rt = self.opts.range
  1379.             if rt[0]: rt = (rt[0] + reget_length, rt[1])
  1380.  
  1381.         if rt:
  1382.             header = range_tuple_to_header(rt)
  1383.             if header:
  1384.                 return header.split('=')[1]
  1385.  
  1386.  
  1387.  
  1388.     def _make_request(self, req, opener):
  1389.         #XXXX
  1390.         # This doesn't do anything really, but we could use this
  1391.         # instead of do_open() to catch a lot of crap errors as 
  1392.         # mstenner did before here
  1393.         return (self.fo, self.hdr)
  1394.         
  1395.         try:
  1396.             if self.opts.timeout:
  1397.                 old_to = socket.getdefaulttimeout()
  1398.                 socket.setdefaulttimeout(self.opts.timeout)
  1399.                 try:
  1400.                     fo = opener.open(req)
  1401.                 finally:
  1402.                     socket.setdefaulttimeout(old_to)
  1403.             else:
  1404.                 fo = opener.open(req)
  1405.             hdr = fo.info()
  1406.         except ValueError, e:
  1407.             err = URLGrabError(1, _('Bad URL: %s : %s') % (self.url, e, ))
  1408.             err.url = self.url
  1409.             raise err
  1410.  
  1411.         except RangeError, e:
  1412.             err = URLGrabError(9, _('%s on %s') % (e, self.url))
  1413.             err.url = self.url
  1414.             raise err
  1415.         except urllib2.HTTPError, e:
  1416.             new_e = URLGrabError(14, _('%s on %s') % (e, self.url))
  1417.             new_e.code = e.code
  1418.             new_e.exception = e
  1419.             new_e.url = self.url
  1420.             raise new_e
  1421.         except IOError, e:
  1422.             if hasattr(e, 'reason') and isinstance(e.reason, socket.timeout):
  1423.                 err = URLGrabError(12, _('Timeout on %s: %s') % (self.url, e))
  1424.                 err.url = self.url
  1425.                 raise err
  1426.             else:
  1427.                 err = URLGrabError(4, _('IOError on %s: %s') % (self.url, e))
  1428.                 err.url = self.url
  1429.                 raise err
  1430.  
  1431.         except OSError, e:
  1432.             err = URLGrabError(5, _('%s on %s') % (e, self.url))
  1433.             err.url = self.url
  1434.             raise err
  1435.  
  1436.         except HTTPException, e:
  1437.             err = URLGrabError(7, _('HTTP Exception (%s) on %s: %s') % \
  1438.                             (e.__class__.__name__, self.url, e))
  1439.             err.url = self.url
  1440.             raise err
  1441.  
  1442.         else:
  1443.             return (fo, hdr)
  1444.         
  1445.     def _do_grab(self):
  1446.         """dump the file to a filename or StringIO buffer"""
  1447.  
  1448.         if self._complete:
  1449.             return
  1450.         _was_filename = False
  1451.         if type(self.filename) in types.StringTypes and self.filename:
  1452.             _was_filename = True
  1453.             self._prog_reportname = str(self.filename)
  1454.             self._prog_basename = os.path.basename(self.filename)
  1455.             
  1456.             if self.append: mode = 'ab'
  1457.             else: mode = 'wb'
  1458.  
  1459.             if DEBUG: DEBUG.info('opening local file "%s" with mode %s' % \
  1460.                                  (self.filename, mode))
  1461.             try:
  1462.                 self.fo = open(self.filename, mode)
  1463.             except IOError, e:
  1464.                 err = URLGrabError(16, _(\
  1465.                   'error opening local file from %s, IOError: %s') % (self.url, e))
  1466.                 err.url = self.url
  1467.                 raise err
  1468.  
  1469.         else:
  1470.             self._prog_reportname = 'MEMORY'
  1471.             self._prog_basename = 'MEMORY'
  1472.  
  1473.             
  1474.             self.fo = StringIO()
  1475.             # if this is to be a tempfile instead....
  1476.             # it just makes crap in the tempdir
  1477.             #fh, self._temp_name = mkstemp()
  1478.             #self.fo = open(self._temp_name, 'wb')
  1479.  
  1480.             
  1481.         self._do_perform()
  1482.         
  1483.  
  1484.  
  1485.         if _was_filename:
  1486.             # close it up
  1487.             self.fo.flush()
  1488.             self.fo.close()
  1489.             # set the time
  1490.             mod_time = self.curl_obj.getinfo(pycurl.INFO_FILETIME)
  1491.             if mod_time != -1:
  1492.                 try:
  1493.                     os.utime(self.filename, (mod_time, mod_time))
  1494.                 except OSError, e:
  1495.                     err = URLGrabError(16, _(\
  1496.                       'error setting timestamp on file %s from %s, OSError: %s') 
  1497.                               % (self.filenameself.url, e))
  1498.                     err.url = self.url
  1499.                     raise err
  1500.             # re open it
  1501.             try:
  1502.                 self.fo = open(self.filename, 'r')
  1503.             except IOError, e:
  1504.                 err = URLGrabError(16, _(\
  1505.                   'error opening file from %s, IOError: %s') % (self.url, e))
  1506.                 err.url = self.url
  1507.                 raise err
  1508.                 
  1509.         else:
  1510.             #self.fo = open(self._temp_name, 'r')
  1511.             self.fo.seek(0)
  1512.  
  1513.         self._complete = True
  1514.     
  1515.     def _fill_buffer(self, amt=None):
  1516.         """fill the buffer to contain at least 'amt' bytes by reading
  1517.         from the underlying file object.  If amt is None, then it will
  1518.         read until it gets nothing more.  It updates the progress meter
  1519.         and throttles after every self._rbufsize bytes."""
  1520.         # the _rbuf test is only in this first 'if' for speed.  It's not
  1521.         # logically necessary
  1522.         if self._rbuf and not amt is None:
  1523.             L = len(self._rbuf)
  1524.             if amt > L:
  1525.                 amt = amt - L
  1526.             else:
  1527.                 return
  1528.  
  1529.         # if we've made it here, then we don't have enough in the buffer
  1530.         # and we need to read more.
  1531.         
  1532.         if not self._complete: self._do_grab() #XXX cheater - change on ranges
  1533.         
  1534.         buf = [self._rbuf]
  1535.         bufsize = len(self._rbuf)
  1536.         while amt is None or amt:
  1537.             # first, delay if necessary for throttling reasons
  1538.             if self.opts.raw_throttle():
  1539.                 diff = self._tsize/self.opts.raw_throttle() - \
  1540.                        (time.time() - self._ttime)
  1541.                 if diff > 0: time.sleep(diff)
  1542.                 self._ttime = time.time()
  1543.                 
  1544.             # now read some data, up to self._rbufsize
  1545.             if amt is None: readamount = self._rbufsize
  1546.             else:           readamount = min(amt, self._rbufsize)
  1547.             try:
  1548.                 new = self.fo.read(readamount)
  1549.             except socket.error, e:
  1550.                 err = URLGrabError(4, _('Socket Error on %s: %s') % (self.url, e))
  1551.                 err.url = self.url
  1552.                 raise err
  1553.  
  1554.             except socket.timeout, e:
  1555.                 raise URLGrabError(12, _('Timeout on %s: %s') % (self.url, e))
  1556.                 err.url = self.url
  1557.                 raise err
  1558.  
  1559.             except IOError, e:
  1560.                 raise URLGrabError(4, _('IOError on %s: %s') %(self.url, e))
  1561.                 err.url = self.url
  1562.                 raise err
  1563.  
  1564.             newsize = len(new)
  1565.             if not newsize: break # no more to read
  1566.  
  1567.             if amt: amt = amt - newsize
  1568.             buf.append(new)
  1569.             bufsize = bufsize + newsize
  1570.             self._tsize = newsize
  1571.             self._amount_read = self._amount_read + newsize
  1572.             #if self.opts.progress_obj:
  1573.             #    self.opts.progress_obj.update(self._amount_read)
  1574.  
  1575.         self._rbuf = string.join(buf, '')
  1576.         return
  1577.  
  1578.     def _progress_update(self, download_total, downloaded, upload_total, uploaded):
  1579.         if self._over_max_size(cur=self._amount_read-self._reget_length):
  1580.             return -1
  1581.  
  1582.         try:
  1583.             if self._prog_running:
  1584.                 downloaded += self._reget_length
  1585.                 self.opts.progress_obj.update(downloaded)
  1586.         except KeyboardInterrupt:
  1587.             return -1
  1588.     
  1589.     def _over_max_size(self, cur, max_size=None):
  1590.  
  1591.         if not max_size:
  1592.             if not self.opts.size:
  1593.                 max_size = self.size
  1594.             else:
  1595.                 max_size = self.opts.size
  1596.  
  1597.         if not max_size: return False # if we have None for all of the Max then this is dumb
  1598.  
  1599.         if cur > int(float(max_size) * 1.10):
  1600.  
  1601.             msg = _("Downloaded more than max size for %s: %s > %s") \
  1602.                         % (self.url, cur, max_size)
  1603.             self._error = (pycurl.E_FILESIZE_EXCEEDED, msg)
  1604.             return True
  1605.         return False
  1606.         
  1607.     def _to_utf8(self, obj, errors='replace'):
  1608.         '''convert 'unicode' to an encoded utf-8 byte string '''
  1609.         # stolen from yum.i18n
  1610.         if isinstance(obj, unicode):
  1611.             obj = obj.encode('utf-8', errors)
  1612.         return obj
  1613.         
  1614.     def read(self, amt=None):
  1615.         self._fill_buffer(amt)
  1616.         if amt is None:
  1617.             s, self._rbuf = self._rbuf, ''
  1618.         else:
  1619.             s, self._rbuf = self._rbuf[:amt], self._rbuf[amt:]
  1620.         return s
  1621.  
  1622.     def readline(self, limit=-1):
  1623.         if not self._complete: self._do_grab()
  1624.         return self.fo.readline()
  1625.         
  1626.         i = string.find(self._rbuf, '\n')
  1627.         while i < 0 and not (0 < limit <= len(self._rbuf)):
  1628.             L = len(self._rbuf)
  1629.             self._fill_buffer(L + self._rbufsize)
  1630.             if not len(self._rbuf) > L: break
  1631.             i = string.find(self._rbuf, '\n', L)
  1632.  
  1633.         if i < 0: i = len(self._rbuf)
  1634.         else: i = i+1
  1635.         if 0 <= limit < len(self._rbuf): i = limit
  1636.  
  1637.         s, self._rbuf = self._rbuf[:i], self._rbuf[i:]
  1638.         return s
  1639.  
  1640.     def close(self):
  1641.         if self._prog_running:
  1642.             self.opts.progress_obj.end(self._amount_read)
  1643.         self.fo.close()
  1644.         
  1645.     def geturl(self):
  1646.         """ Provide the geturl() method, used to be got from
  1647.             urllib.addinfourl, via. urllib.URLopener.* """
  1648.         return self.url
  1649.         
  1650. _curl_cache = pycurl.Curl() # make one and reuse it over and over and over
  1651.  
  1652. def reset_curl_obj():
  1653.     """To make sure curl has reread the network/dns info we force a reload"""
  1654.     global _curl_cache
  1655.     _curl_cache.close()
  1656.     _curl_cache = pycurl.Curl()
  1657.  
  1658.  
  1659.     
  1660.  
  1661. #####################################################################
  1662. # DEPRECATED FUNCTIONS
  1663. def set_throttle(new_throttle):
  1664.     """Deprecated. Use: default_grabber.throttle = new_throttle"""
  1665.     default_grabber.throttle = new_throttle
  1666.  
  1667. def set_bandwidth(new_bandwidth):
  1668.     """Deprecated. Use: default_grabber.bandwidth = new_bandwidth"""
  1669.     default_grabber.bandwidth = new_bandwidth
  1670.  
  1671. def set_progress_obj(new_progress_obj):
  1672.     """Deprecated. Use: default_grabber.progress_obj = new_progress_obj"""
  1673.     default_grabber.progress_obj = new_progress_obj
  1674.  
  1675. def set_user_agent(new_user_agent):
  1676.     """Deprecated. Use: default_grabber.user_agent = new_user_agent"""
  1677.     default_grabber.user_agent = new_user_agent
  1678.     
  1679. def retrygrab(url, filename=None, copy_local=0, close_connection=0,
  1680.               progress_obj=None, throttle=None, bandwidth=None,
  1681.               numtries=3, retrycodes=[-1,2,4,5,6,7], checkfunc=None):
  1682.     """Deprecated. Use: urlgrab() with the retry arg instead"""
  1683.     kwargs = {'copy_local' :  copy_local, 
  1684.               'close_connection' : close_connection,
  1685.               'progress_obj' : progress_obj, 
  1686.               'throttle' : throttle, 
  1687.               'bandwidth' : bandwidth,
  1688.               'retry' : numtries,
  1689.               'retrycodes' : retrycodes,
  1690.               'checkfunc' : checkfunc 
  1691.               }
  1692.     return urlgrab(url, filename, **kwargs)
  1693.  
  1694.         
  1695. #####################################################################
  1696. #  TESTING
  1697. def _main_test():
  1698.     try: url, filename = sys.argv[1:3]
  1699.     except ValueError:
  1700.         print 'usage:', sys.argv[0], \
  1701.               '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
  1702.         sys.exit()
  1703.  
  1704.     kwargs = {}
  1705.     for a in sys.argv[3:]:
  1706.         k, v = string.split(a, '=', 1)
  1707.         kwargs[k] = int(v)
  1708.  
  1709.     set_throttle(1.0)
  1710.     set_bandwidth(32 * 1024)
  1711.     print "throttle: %s,  throttle bandwidth: %s B/s" % (default_grabber.throttle, 
  1712.                                                         default_grabber.bandwidth)
  1713.  
  1714.     try: from progress import text_progress_meter
  1715.     except ImportError, e: pass
  1716.     else: kwargs['progress_obj'] = text_progress_meter()
  1717.  
  1718.     try: name = apply(urlgrab, (url, filename), kwargs)
  1719.     except URLGrabError, e: print e
  1720.     else: print 'LOCAL FILE:', name
  1721.  
  1722.  
  1723. def _retry_test():
  1724.     try: url, filename = sys.argv[1:3]
  1725.     except ValueError:
  1726.         print 'usage:', sys.argv[0], \
  1727.               '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
  1728.         sys.exit()
  1729.  
  1730.     kwargs = {}
  1731.     for a in sys.argv[3:]:
  1732.         k, v = string.split(a, '=', 1)
  1733.         kwargs[k] = int(v)
  1734.  
  1735.     try: from progress import text_progress_meter
  1736.     except ImportError, e: pass
  1737.     else: kwargs['progress_obj'] = text_progress_meter()
  1738.  
  1739.     def cfunc(filename, hello, there='foo'):
  1740.         print hello, there
  1741.         import random
  1742.         rnum = random.random()
  1743.         if rnum < .5:
  1744.             print 'forcing retry'
  1745.             raise URLGrabError(-1, 'forcing retry')
  1746.         if rnum < .75:
  1747.             print 'forcing failure'
  1748.             raise URLGrabError(-2, 'forcing immediate failure')
  1749.         print 'success'
  1750.         return
  1751.         
  1752.     kwargs['checkfunc'] = (cfunc, ('hello',), {'there':'there'})
  1753.     try: name = apply(retrygrab, (url, filename), kwargs)
  1754.     except URLGrabError, e: print e
  1755.     else: print 'LOCAL FILE:', name
  1756.  
  1757. def _file_object_test(filename=None):
  1758.     import cStringIO
  1759.     if filename is None:
  1760.         filename = __file__
  1761.     print 'using file "%s" for comparisons' % filename
  1762.     fo = open(filename)
  1763.     s_input = fo.read()
  1764.     fo.close()
  1765.  
  1766.     for testfunc in [_test_file_object_smallread,
  1767.                      _test_file_object_readall,
  1768.                      _test_file_object_readline,
  1769.                      _test_file_object_readlines]:
  1770.         fo_input = cStringIO.StringIO(s_input)
  1771.         fo_output = cStringIO.StringIO()
  1772.         wrapper = PyCurlFileObject(fo_input, None, 0)
  1773.         print 'testing %-30s ' % testfunc.__name__,
  1774.         testfunc(wrapper, fo_output)
  1775.         s_output = fo_output.getvalue()
  1776.         if s_output == s_input: print 'passed'
  1777.         else: print 'FAILED'
  1778.             
  1779. def _test_file_object_smallread(wrapper, fo_output):
  1780.     while 1:
  1781.         s = wrapper.read(23)
  1782.         fo_output.write(s)
  1783.         if not s: return
  1784.  
  1785. def _test_file_object_readall(wrapper, fo_output):
  1786.     s = wrapper.read()
  1787.     fo_output.write(s)
  1788.  
  1789. def _test_file_object_readline(wrapper, fo_output):
  1790.     while 1:
  1791.         s = wrapper.readline()
  1792.         fo_output.write(s)
  1793.         if not s: return
  1794.  
  1795. def _test_file_object_readlines(wrapper, fo_output):
  1796.     li = wrapper.readlines()
  1797.     fo_output.write(string.join(li, ''))
  1798.  
  1799. if __name__ == '__main__':
  1800.     _main_test()
  1801.     _retry_test()
  1802.     _file_object_test('test')
  1803.